package cn.jiangjiesheng.bootstrap.filestore.impl;

import cn.jiangjiesheng.bootstrap.filestore.exception.InvalidLocalFileException;
import cn.jiangjiesheng.bootstrap.filestore.properties.FileStoreProperties;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import cn.jiangjiesheng.bootstrap.filestore.dto.ResultMap;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 华为云OBS
 * <p>
 * Created by dev@jiangjiesheng.cn on 2021/7/9  <br/>
 */
@Slf4j
public class HuaweiObsFileStore extends AbstractFileStore<ObsClient> {

    public HuaweiObsFileStore(FileStoreProperties fileStoreProperties) {
        log.debug("Initializing huawei cloud obs client");
        this.setFileStoreProperties(fileStoreProperties);
        initializeClient(fileStoreProperties);
    }

    @Override
    public ObsClient initializeClient(FileStoreProperties properties) {
        setStoreClient(new ObsClient(properties.getAk(), properties.getSk(), properties.getEndpoint()));
        return getStoreClient(ObsClient.class);
    }

    /**
     * 分段上传
     * https://support.huaweicloud.com/sdk-android-devg-obs/obs_26_0407.html
     *
     * @param bucket
     * @param objectId
     * @param filepath
     */
    @Override
    public String putBigObject(String bucket, String objectId, String filepath) {
        final ObsClient obsClient = getStoreClient(ObsClient.class);
        // 初始化线程池
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        final File largeFile = new File(filepath);
        if (!largeFile.isFile()) {
            throw new InvalidLocalFileException();
        }
        // 初始化分段上传任务
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucket, objectId);
        InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);

        final String uploadId = result.getUploadId();
        log.info("UploadPart {}", uploadId + "\n");

        // 每段上传50MB
        long partSize = 10 * 1024 * 1024L;
        long fileSize = largeFile.length();
        // 计算需要上传的段数
        long partCount = fileSize % partSize == 0 ? fileSize / partSize : fileSize / partSize + 1;
        final List<PartEtag> partEtags = Collections.synchronizedList(new ArrayList<PartEtag>());
        // 执行并发上传段
        for (int i = 0; i < partCount; i++) {
            // 分段在文件中的起始位置
            final long offset = i * partSize;
            // 分段大小
            final long currPartSize = (i + 1 == partCount) ? fileSize - offset : partSize;
            // 分段号
            final int partNumber = i + 1;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    UploadPartRequest uploadPartRequest = new UploadPartRequest();
                    uploadPartRequest.setBucketName(bucket);
                    uploadPartRequest.setObjectKey(objectId);
                    uploadPartRequest.setUploadId(uploadId);
                    uploadPartRequest.setFile(largeFile);
                    uploadPartRequest.setPartSize(currPartSize);
                    uploadPartRequest.setOffset(offset);
                    uploadPartRequest.setPartNumber(partNumber);
                    UploadPartResult uploadPartResult;
                    try {
                        uploadPartResult = obsClient.uploadPart(uploadPartRequest);
                        log.info("UploadPart Part# {} done", partNumber);
                        partEtags.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()));
                    } catch (ObsException e) {
                        log.error("UploadPart {} {}", e.getMessage(), e);
                    }
                }
            });
        }
        // 等待上传完成
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            try {
                executorService.awaitTermination(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error("UploadPart {} {}", e.getMessage(), e);
            }
        }
        // 合并段
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucket, objectId, uploadId, partEtags);
        obsClient.completeMultipartUpload(completeMultipartUploadRequest);
        return uploadId;
    }

    /**
     * 分段上传
     *
     * @param objectId
     * @param filepath
     */
    @Override
    public String putBigObject(String objectId, String filepath) {
        return this.putBigObject(getFileStoreProperties().getDefaultBucket(), objectId, filepath);
    }

    @Override
    public void putObject(String bucket, String objectId, InputStream fileData) {
        getStoreClient(ObsClient.class).putObject(bucket, objectId, fileData);
    }


    @Override
    public InputStream getObject(String bucket, String objectId) {
        return getStoreClient(ObsClient.class).getObject(bucket, objectId).getObjectContent();
    }

    @Override
    public void publicRead(String bucket, String objectId, boolean enabled) {
        if (enabled) {
            getStoreClient(ObsClient.class).setObjectAcl(bucket, objectId, AccessControlList.REST_CANNED_PUBLIC_READ);
        } else {
            getStoreClient(ObsClient.class).setObjectAcl(bucket, objectId, AccessControlList.REST_CANNED_PRIVATE);
        }
    }


    @Override
    public void copyObject(String bucket, String objectId, String targetBucket, String targetObjectId) {
        getStoreClient(ObsClient.class).copyObject(bucket, objectId, targetBucket, targetObjectId);
    }


    @Override
    public Boolean doesBucketExists(String bucket) {
        return getStoreClient(ObsClient.class).headBucket(bucket);
    }


    @Override
    public void createBucket(String bucket) {
        getStoreClient(ObsClient.class).createBucket(bucket);
    }


    @Override
    public void deleteObject(String bucket, String objectId) {
        getStoreClient(ObsClient.class).deleteObject(bucket, objectId);
    }


    @Override
    public ResultMap deleteMultipleObjects(String bucket, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            deleteObject(bucket, objectId);
            resultMap.put(objectId, true);
        }
        return resultMap;
    }


    @Override
    public ResultMap doesObjectsExists(String bucket, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            resultMap.put(objectId, doesObjectExists(bucket, objectId));
        }
        return resultMap;
    }


    @Override
    public Boolean doesObjectExists(String bucket, String objectId) {
        return getStoreClient(ObsClient.class).doesObjectExist(bucket, objectId);
    }


    @Override
    public ResultMap getObjectMeta(String bucket, String objectId) {
        ResultMap resultMap = new ResultMap();
        ObjectMetadata objectMetadata = getStoreClient(ObsClient.class).getObjectMetadata(bucket, objectId);
        resultMap.put("lastModified", objectMetadata.getLastModified());
        resultMap.put("size", objectMetadata.getContentLength());
        resultMap.put("contentType", objectMetadata.getContentType());
        return resultMap;
    }


    @Override
    public ResultMap generatePreSignedURL(String bucket, String... objectIds) {
        return generatePreSignedURL(bucket, new Date(new Date().getTime() + 3600 * 1000), objectIds);
    }


    @Override
    public ResultMap generatePreSignedURL(String bucket, Date expiration, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            String signedUrl = getStoreClient(ObsClient.class).createGetTemporarySignature(bucket,
                    objectId, "", expiration, null, null).getSignedUrl();
            resultMap.put(objectId, signedUrl);
        }
        return resultMap;
    }

    @Override
    public void putObject(String objectId, InputStream fileData) {
        this.putObject(this.getFileStoreProperties().getDefaultBucket(), objectId, fileData);
    }

    @Override
    public InputStream getObject(String objectId) {
        return this.getObject(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public void deleteObject(String objectId) {
        this.deleteObject(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap deleteMultipleObjects(String... objectIds) {
        return this.deleteMultipleObjects(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public ResultMap doesObjectsExists(String... objectIds) {
        return this.doesObjectsExists(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public Boolean doesObjectExists(String objectId) {
        return this.doesObjectExists(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap getObjectMeta(String objectId) {
        return this.getObjectMeta(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap generatePreSignedURL(String... objectIds) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public ResultMap generatePreSignedURL(Date expiration, String... objectIds) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), expiration, objectIds);
    }

    @Override
    public ResultMap generatePreSignedURL(String objectId) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), new String[]{objectId});
    }


    @Override
    public void close() {
        try {
            getStoreClient(ObsClient.class).close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
